﻿using System;
using System.Collections.Generic;
using System.Text;
using MvvmFoundation.Wpf;

namespace RLProject1.Model {
    /// <summary>
    /// Repräsentiert ein Zustand (Feld) mit den zugehörigen Q-Values zu allen Aktionen.
    /// <para>Unterstützt Iteration und PropChange-Notification.</para>
    /// <para>Unterstützt die Operatoren == und !=</para>
    /// </summary>
    public class GridStateQValues : ObservableObject, IEnumerable<double> {

        /// <summary>Die Q-Values für diesen Zustand</summary>
        double[] _values;

        // for conveniance:
        static readonly GridActionTypes[] _PROPTYPES = (GridActionTypes[])Enum.GetValues(typeof(GridActionTypes));


        #region Constructors

        /// <summary>
        /// Erzeugt Default-QValues für den angegebenen Zustand.<br />
        /// Für alle <see cref="GridAction"/> ist der Q-Wert gleich 0. 
        /// </summary>
        /// <param name="state">zugehörige Zustands-ID</param>
        /// <seealso cref="GridActionTypes"/>
        public GridStateQValues(GridState state)
            : this(state, null) {
        }

        /// <summary>
        /// Neue Instanz für den angegebenen Zustand mit den spezifizierten Initial-Q-Werten. <br />
        /// Die Reihenfolge der Q-Werte muss der, der <see cref="GridActionTypes"/> entsprechen. Falls
        /// weniger Initialwerte als GridActionTypes angegeben werden, sind sie übrigen automatisch gleich 0.
        /// </summary>
        /// <param name="state">zugehörige Zustands-ID</param>
        /// <param name="values">initiale Q-Werte für die <see cref="GridAction">GridActions</see></param>
        public GridStateQValues(GridState state, params double[] values)
            : base() {
            State = state;
            _values = new double[_PROPTYPES.Length];
            if (values != null)
                for (int i = 0; i < Math.Min(_values.Length, values.Length); i++)
                    _values[i] = values[i];
        }

        #endregion

        #region Items & Properties

        /// <summary>
        /// Gibt oder setzt den Q-Value zur spezifizerten Aktion.
        /// </summary>
        /// <value>Double-Schätzwert Q (action value)</value>
        /// <param name="action">GridAction</param>
        /// <returns>Q-Value</returns>
        public double this[GridAction action] {
            get { return _values[action]; }
            set {
                _values[action] = value;
                base.RaisePropertyChanged(action.Direction.ToString());
            }
        }

        /// <summary>
        /// Gibt oder setzt den Q-Value für die Aktion 'Gehe Nord'
        /// </summary>
        public double N {
            get { return _values[(int)GridActionTypes.N]; }
            set {
                _values[(int)GridActionTypes.N] = value;
                RaisePropertyChanged("N");
            }
        }
        /// <summary>
        /// Gibt oder setzt den Q-Value für die Aktion 'Gehe Nordost'
        /// </summary>
        public double NO {
            get { return _values[(int)GridActionTypes.NO]; }
            set {
                _values[(int)GridActionTypes.NO] = value;
                RaisePropertyChanged("NO");
            }
        }
        /// <summary>
        /// Gibt oder setzt den Q-Value für die Aktion 'Gehe Ost'
        /// </summary>
        public double O {
            get { return _values[(int)GridActionTypes.O]; }
            set {
                _values[(int)GridActionTypes.O] = value;
                RaisePropertyChanged("O");
            }
        }
        /// <summary>
        /// Gibt oder setzt den Q-Value für die Aktion 'Gehe Südost'
        /// </summary>
        public double SO {
            get { return _values[(int)GridActionTypes.SO]; }
            set {
                _values[(int)GridActionTypes.SO] = value;
                RaisePropertyChanged("SO");
            }
        }
        /// <summary>
        /// Gibt oder setzt den Q-Value für die Aktion 'Gehe Süd'
        /// </summary>
        public double S {
            get { return _values[(int)GridActionTypes.S]; }
            set {
                _values[(int)GridActionTypes.S] = value;
                RaisePropertyChanged("S");
            }
        }
        /// <summary>
        /// Gibt oder setzt den Q-Value für die Aktion 'Gehe Südwest'
        /// </summary>
        public double SW {
            get { return _values[(int)GridActionTypes.SW]; }
            set {
                _values[(int)GridActionTypes.SW] = value;
                RaisePropertyChanged("SW");
            }
        }
        /// <summary>
        /// Gibt oder setzt den Q-Value für die Aktion 'Gehe West'
        /// </summary>
        public double W {
            get { return _values[(int)GridActionTypes.W]; }
            set {
                _values[(int)GridActionTypes.W] = value;
                RaisePropertyChanged("W");
            }
        }
        /// <summary>
        /// Gibt oder setzt den Q-Value für die Aktion 'Gehe Nordwest'
        /// </summary>
        public double NW {
            get { return _values[(int)GridActionTypes.NW]; }
            set {
                _values[(int)GridActionTypes.NW] = value;
                RaisePropertyChanged("NW");
            }
        }

        /// <summary>
        /// Gibt alle Q-Werte dieses Zustands (GridQValues) in der Reihenfolge
        /// der <see cref="GridActionTypes"/>.
        /// </summary>
        public double[] All {
            get { return (double[])_values.Clone(); }
        }

        /// <summary>
        /// Gibt den Zustand (das Feld), mit dem diese Q-Values assoziiert sind.
        /// </summary>
        public GridState State { get; private set; }

        #endregion


        #region IEnumerable<double> Members

        public IEnumerator<double> GetEnumerator() {
            for (int i = 0; i < _values.Length; i++) {
                yield return _values[i];
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }

        #endregion

        #region Object-Overrides and operators

        public override string ToString() {
            StringBuilder s = new StringBuilder("GridStateQValues[");
            foreach (double v in _values) {
                s.Append((Math.Round(v * 100) / 100.0) + ",");
            }
            s.Remove(s.Length - 1, 1);
            s.Append("]");
            return s.ToString();
        }

        public override int GetHashCode() {
            return _values.GetHashCode();
        }

        public override bool Equals(object obj) {
            if (obj is GridStateQValues)
                return this.State == (obj as GridStateQValues).State;
            return false;
        }
        /// <summary>
        /// Zwei GridStateQValues sind identisch, wenn sie den selben Zustand repräsentieren und
        /// dieselben Q-Werte haben.
        /// </summary>
        /// <param name="a">first Operand</param>
        /// <param name="b">second operand</param>
        /// <returns>true, if State and all q-values are identical</returns>
        public static bool operator ==(GridStateQValues a, GridStateQValues b) {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(a, b))
                return true;
            // If one is null, but not both, return false.
            if (((object)a == null) || ((object)b == null))
                return false;
            // if fields !=, return false.
            if (a.State != b.State)
                return false;
            if (a._values.Length != b._values.Length)
                return false;
            for (int i = 0; i < a._values.Length; i++) {
                if (a._values[i] != b._values[i])
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Testet auf Nicht-Identität: unterscheiden sich Zustand und/oder Q-Werte? 
        /// </summary>
        /// <param name="a">erster Operand</param>
        /// <param name="b">zweiter Operand</param>
        /// <returns>true, wenn sich Zustand und/oder Q-Werte unterscheiden</returns>
        public static bool operator !=(GridStateQValues a, GridStateQValues b) {
            return !(a == b);
        }

        #endregion
    }

}
